﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;


//per file
using System.IO;
//per richiamo dll
using System.Runtime.InteropServices;

//thread
using System.Threading;


namespace BasicWave
{

    /// <summary>
    /// qUESTa classe si occupa di decodificare un mp3 e convertirlo in impulsi
    /// pcm gestibili dal buffer della scheda audio del computer
    /// <remarks>LA classe in data 1 settembre funziona...è una alpha..ma funziona...
    /// quindi non c'è bisogno di stravolgerla....
    /// se ci sono problemi..NON SONO QUI
    /// </remarks>
    /// </summary>
    public class CMp3Channel : CBaseAudioChannel, IDisposable, IAudioChannel
    {

        // The easy one
        public enum madx_sig : int
        {
            ERROR_OCCURED,
            MORE_INPUT,
            FLUSH_BUFFER,
            EOF_REACHED,
            CALL_AGAIN
        }



#region "Function DLL MdxLib"
        [StructLayout(LayoutKind.Sequential, Size = 276), Serializable]
        public struct madx_stat
        {

            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
            public string msg;
            public uint write_size;
            public int is_eof;
            public uint readsize;
            public uint remaining;
            public IntPtr buffstart;

        }






        [DllImport("madxlib.dll")]
        public static extern int
        madx_init(IntPtr out_buffer, IntPtr mx_house);



        [DllImport("madxlib.dll")]
        public static extern madx_sig
        madx_read(IntPtr in_buffer,
            IntPtr out_buffer, IntPtr mx_house, ref madx_stat mxstat);



        [DllImport("madxlib.dll")]
        public static extern void
        madx_deinit(IntPtr mx_house);
#endregion


        Queue<byte> m_fifo = new Queue<byte>();


        CMasterChannel m_master;



        //QUESTI VALORI SONO FISSI..NON SI POSSONO CAMBIARE
        //LA LIBRERIA HA BISOGNO DI QUESTI ESATTI VALORI PER FUNZIONARE
        public const int MADX_OUTPUT_BUFFER_SIZE = (1152 * 8); // = 9216
        public const int MADX_INPUT_BUFFER_SIZE = (5 * 1152 * 8);


        // Gotten from sizeof(madx_house):
        public const int MADX_HOUSE_SIZE = 22672;



        //i buffer usati dal decoder per memorizzare i dati
        //entranti ed uscenti della codifica
        byte[] inBuffer;
        byte[] outBuffer;
        byte[] mxHouse;


        //puntatori all'area di memoria allocata nella zona Gestita
        //servono per poter poi deallocare la zona una volta smesso di utilizzarla
        GCHandle handleA;
        GCHandle handleB;
        GCHandle handle;


        //puntatori all'area di memoria allocata nella zona NON Gestita
        //servono per poter poi deallocare la zona una volta smesso di utilizzarla
        IntPtr outBufferPtr;
        IntPtr inBufferPtr;
        IntPtr mxHousePtr;


        madx_sig mxSignal;
        madx_stat mxStat = new madx_stat();

        
        //percorso del file che si vuole decodificare
        string file_mp3;

        //stream che punta al file aperto
        Stream s;
        //il binary reader che legge fisicamente i dati sul file
        BinaryReader br;

        Thread t_decode;


        public CMp3Channel(CMasterChannel aMaster, string aFile)
        {


            //assegno parametro a master channel
            m_master = aMaster;

            m_master.RegisteredChan(this);
           

            //controllo dell'esistenza del file
            //se non esiste il file..esce immediatamente
            if (!File.Exists(aFile))
                throw new Exception("Il file non esiste");

            file_mp3 = aFile;


            //inizializza le strutture della dll
            InitMdxLib();


            //il thread che si occupa di decodificare il file
            t_decode = new Thread(new ThreadStart(DecodeThread));


            // open inputFile as binary
            s =  new FileStream(file_mp3, FileMode.Open, FileAccess.Read);
            br = new BinaryReader(s);




        }

        private void InitMdxLib()
        {
            // Trickery to get around limitation in .NET
            // with structs and arrays (couldn't map 
            // madx_house struct directly).

            mxHouse = new byte[MADX_HOUSE_SIZE];
            handle = GCHandle.Alloc(mxHouse, GCHandleType.Pinned);
            mxHousePtr = handle.AddrOfPinnedObject();



            // Pin output buffer, assign to pointer

            outBuffer = new byte[MADX_OUTPUT_BUFFER_SIZE];
            handleA =
                GCHandle.Alloc(outBuffer, GCHandleType.Pinned);
             outBufferPtr = handleA.AddrOfPinnedObject();



            // Pin input buffer, assign to pointer

            inBuffer = new byte[MADX_INPUT_BUFFER_SIZE];
            handleB =
                GCHandle.Alloc(inBuffer, GCHandleType.Pinned);
            inBufferPtr = handleB.AddrOfPinnedObject();



            // Initialize madxlib

            if ((madx_init(outBufferPtr, mxHousePtr)) == 0)
            {
                throw new Exception("Errore inizializzazione madx init");
            }
        }

        /// <summary>
        /// implementazione concreta dell'interfaccia! 
        /// Questa funzione viene chiamata ogni qual volta il master
        /// riceve dalla scheda madre il comando di "filler" riempimento
        /// del buffer....a sua volta il master fa lo stesso con tutti i suoi figli
        /// 
        /// </summary>
        /// <param name="aBuffer"></param>
        /// <param name="size"></param>
        public void FillChunk(ref byte[] aBuffer, int size)
        {
            lock (m_fifo)
            {

              

                for (int i = 0; i < size; i++)
                    if (m_fifo.Count > 0)
                        aBuffer[i] = m_fifo.Dequeue();


                //prima di consegnarli al master.... applicare il volume
                VolumerizeByte(ref aBuffer, size);
            }
        }





        /// <summary>
        /// Funzione che avvia la decodifica del thread e la conseguenzte riproduzione
        /// del file mp3 decodificato. Avvia il thread di decodifica...
        /// 
        /// </summary>
        public void Play()
        {
           if (t_decode.ThreadState == ThreadState.Unstarted)
              t_decode.Start();
        }


        /// <summary>
        /// Ferma la riproduzione dell'mp3 . La funzione interrompe il thread!
        /// </summary>
        public void Stop()
        {
            t_decode.Interrupt();
        }



       



        /// <summary>
        ///questa è la funzione che vieen chiamata ripetitivamente dal thread.
        //la funzione si occupa di decodificare l'mp3 ed inserire gli impulsi pcm 
        //generati nel master buffer...
        /// </summary>
        private void DecodeThread()
        {


            do
            {

                mxSignal = madx_read(
                    inBufferPtr, outBufferPtr, mxHousePtr, ref mxStat);



                //un log interno ..potrebbe essere utile
                //if (mxStat.msg != "") Console.WriteLine("{0}", mxStat.msg);




                if (mxSignal == madx_sig.ERROR_OCCURED)			// Error
                {


                    throw new Exception("Errore nel ciclo di decodifica");



                }
                else if (mxSignal == madx_sig.MORE_INPUT)		// Input
                {



                    // Partial read

                    if ((int)mxStat.buffstart != 0)
                    {

                        int a;
                        if ((a = br.Read(inBuffer, (int)(MADX_INPUT_BUFFER_SIZE - mxStat.readsize), (int)mxStat.readsize)) == (int)mxStat.readsize)
                        {
                            //Console.WriteLine("Partial buffer {0}", a);
                        }
                        else
                        {
                            if (s.Position == s.Length) mxStat.is_eof = 1;
                            mxStat.readsize = (uint)a;
                        }


                    }
                    else		// Full read
                    {

                        int a;
                        if ((a = br.Read(inBuffer, 0, MADX_INPUT_BUFFER_SIZE)) == (int)mxStat.readsize)
                        {
                            //Console.WriteLine("Full buffer {0}", a);
                        }
                        else
                        {
                            if (s.Position == s.Length) mxStat.is_eof = 1;
                            mxStat.readsize = (uint)a;
                        }


                    }




                }
                else if (mxSignal == madx_sig.FLUSH_BUFFER) 	// Write
                {


                   

                    //vecchia chiamata alla funzione...da eliminare....
                    //m_master.EnquedRawData(ref outBuffer, 0, (int)mxStat.write_size);



                    //qui mi vengono restituiti i byte pcm decodificati dal decoder
                    //la funzione inserisce i dati nel master buffer
                    lock (m_fifo)
                    {
                       

                        //aggiunge dati al buffer 
                        for (int i = 0; i < mxStat.write_size; i++)
                            m_fifo.Enqueue(outBuffer[i]);
                    }



                }
                else if (mxSignal == madx_sig.EOF_REACHED) 		// End
                {

                    t_decode.Interrupt();


                    //Console.WriteLine("Finished. {0}", (int)mxStat.write_size);

                }





            } while (true);

        }

      
        //dealloca tutte le strutture inizializzate
        public void Dispose()
        {
            handle.Free();
            handleA.Free();
            handleB.Free();

            s.Close();
            br.Close();

            t_decode.Abort();
           
        }



      




    }
}
